iT邦幫忙

2023 iThome 鐵人賽

DAY 15
1
AI & Data

ML From Scratch系列 第 15

[Day 15] Gaussian Mixture Model — 主題實作

  • 分享至 

  • xImage
  •  

我們今天會完成 Gaussian Mixture Model 實做的部份。

Implementation

Import Library

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import multivariate_normal
import cv2
from PIL import Image

GMM from scratch

class GaussianMixture:
    """Gaussian Mixture Model
    initial centrol using k-meas solution.
    """
    def __init__(self, k=3, max_iter=100):
        self.k = k
        self.max_iter = max_iter
        self.loglikelihood = []
        
    def init(self, X, parameters=None):
        self.X = X 
        self.sample_size, self.n_features = X.shape # (76480, 3)
        
        if parameters is not None:
            centroid, labels = parameters
            pi = [np.mean(labels == k) for k in range(self.k)] # (3, )
            mu = centroid # (3, 3)
            cov = [np.cov(X[np.where(labels==k)[0]].T) for k in range(self.k)] # (3, 3, 3)
        
        return np.array(pi), np.array(mu), np.array(cov)
    
    def EM(self, pi, mu, cov):
        
        # initialization
        self.pi, self.mu, self.cov = pi, mu, cov
        
        for n_iter in range(self.max_iter):
            # E step
            self._resp, self.likelihood = self._e_step(self.pi, self.mu, self.cov)
            # M step
            self.pi, self.mu, self.cov = self._m_step(self._resp)
            # recode log likelihood
            self.recode_likelihood(self.likelihood)
            
        return self.likelihood
            
    def _e_step(self, pi, mu, cov):
        # calculate likelihood
        likelihood = np.array([multivariate_normal.pdf(self.X, mean=mu[k], cov=cov[k], allow_singular=True) for k in range(self.k)]) # (3, 76480)
        resp = np.array(likelihood / np.sum(likelihood, axis=0)).T # (76480, 3)
        return resp, likelihood
    
    def _m_step(self, resp):
        N_k = np.sum(resp, axis=0) # (3,)
        pi = N_k / self.sample_size # (3,)
        mu = np.dot(resp.T, self.X) /  N_k[:, np.newaxis] # (3, 3)
        cov = [ (1/N_k[k]) * np.dot((resp[:, k, np.newaxis]*(self.X - mu[k])).T, (self.X - mu[k]))  for k in range(self.k)]
        return pi, mu, cov
        
    def recode_likelihood(self, likelihood):
        # log likelihood    
        loglikelihood = np.sum(np.log(np.sum(likelihood, axis=0)))
        self.loglikelihood.append(loglikelihood)

    def predict(self, X):
        likelihood = np.array([self.pi[k]*self.N(self.mu[k], self.cov[k]) for k in range(self.k)])
        return np.argmax(likelihood, axis=0)
    
    def plot_image(self, image, likelihood):
        high, width, color_shape = image.shape
        idx = np.argmax(likelihood, axis=0)
        img = (self.mu[idx]*255).astype(int)
        img = Image.fromarray(img.reshape(high, width, color_shape).astype('uint8'))
        plt.axis('off')
        plt.imshow(img)
        plt.show()

__init__(self, k, max_iter)

初始化高斯混合模型,可以設定混合成分數 (k) 和最大迭代次數 (max_iter),並初始化記錄對數似然值的空列表。

init(self, X, parameters)

初始化模型參數。

它接受輸入數據 X,以及可選的初始化參數 parameters。如果提供了初始化參數,它將使用這些參數,否則將隨機初始化模型的參數(混合比例 pi、均值 mu 和協方差矩陣 cov)。

EM(self, pi, mu, cov)

實現期望最大化 (Expectation-Maximization, EM) 算法,用於訓練高斯混合模型。它迭代進行 E 步和 M 步,計算混合成分的參數(混合比例 pi、均值 mu 和協方差矩陣 cov),並記錄對數似然值。

  • _e_step 方法實現 EM 算法中的 E 步。它計算每個數據點屬於每個混合成分的概率,並計算整體似然值。
  • _m_step 方法實現 EM 算法中的 M 步。它使用在 E 步中計算的概率,更新混合成分的參數。

recode_likelihood(self, likelihood)

用於記錄每次迭代的對數似然值,這有助於監控模型訓練的進展。

predict(self, X)

用於根據訓練好的模型參數對新數據進行預測,它計算每個混合成分生成新數據的概率,然後選擇概率最高的混合成分作為預測結果。

plot_image(self, image, likelihood)

用於根據模型的似然值來繪製圖像。

它根據每個數據點在混合成分中的概率,選擇最有可能的混合成分,然後將其均值轉換為圖像像素值並顯示出來。

KMeans from scratch

np.random.seed(42)

def euclidean_distance(x1, x2):
    return np.sqrt(np.sum((x1 - x2)**2))

class KMeans():
    def __init__(self, K=5, max_iters=100, plot_steps=False):
        self.K = K
        self.max_iters = max_iters
        self.plot_steps = plot_steps
        # list of sample indices for each cluster
        self.clusters = [[] for _ in range(self.K)]
        # the centers (mean feature vector) for each cluster
        self.centroids = []
        
    def predict(self, X):
        self.X = X
        self.n_samples, self.n_features = X.shape
        
        # initialize 
        random_sample_idxs = np.random.choice(self.n_samples, self.K, replace=False)
        self.centroids = [self.X[idx] for idx in random_sample_idxs]
        # Optimize clusters
        for _ in range(self.max_iters):
            # Assign samples to closest centroids (create clusters)
            self.clusters = self._create_clusters(self.centroids)
            if self.plot_steps:
                self.plot()
            # Calculate new centroids from the clusters
            centroids_old = self.centroids
            self.centroids = self._get_centroids(self.clusters)
            
            # check if clusters have changed
            if self._is_converged(centroids_old, self.centroids):
                break
            if self.plot_steps:
                self.plot()
        # Classify samples as the index of their clusters
        return self._get_cluster_labels(self.clusters)
    def _get_cluster_labels(self, clusters):
        # each sample will get the label of the cluster it was assigned to
        labels = np.empty(self.n_samples)
        for cluster_idx, cluster in enumerate(clusters):
            labels[cluster] = cluster_idx
        return labels
    def _create_clusters(self, centroids):
        # Assign the samples to the closest centroids to create clusters
        clusters = [[] for _ in range(self.K)]
        for idx, sample in enumerate(self.X):
            centroid_idx = self._closest_centroid(sample, centroids)
            clusters[centroid_idx].append(idx)
        return clusters
    def _closest_centroid(self, sample, centroids):
        # distance of the current sample to each centroid
        distances = [euclidean_distance(sample, point) for point in centroids]
        closest_index = np.argmin(distances)
        return closest_index
    def _get_centroids(self, clusters):
        # assign mean value of clusters to centroids
        centroids = np.zeros((self.K, self.n_features))
        for cluster_idx, cluster in enumerate(clusters):
            cluster_mean = np.mean(self.X[cluster], axis=0)
            centroids[cluster_idx] = cluster_mean
        return centroids
    def _is_converged(self, centroids_old, centroids):
        # distances between each old and new centroids, fol all centroids
        distances = [euclidean_distance(centroids_old[i], centroids[i]) for i in range(self.K)]
        return sum(distances) == 0
    def plot(self):
        fig, ax = plt.subplots(figsize=(12, 8))
        for i, index in enumerate(self.clusters):
            point = self.X[index].T
            ax.scatter(*point)
        for point in self.centroids:
            ax.scatter(*point, marker="x", color='black', linewidth=2)
        plt.show()
    def cent(self):
        return self.centroids

__init__(self, K, max_iters, plot_steps)

初始化 K 值(要聚類的群數)、最大迭代次數、是否顯示步驟性的圖形等參數。它也初始化了用於存儲每個簇的樣本索引和每個簇的中心點。

predict(self, X)

用於執行 K-Means 聚類。它首先隨機初始化 K 個中心點,然後反覆執行以下步驟:

  • 將樣本分配到最接近的中心點以創建簇。
  • 從新簇中計算新的中心點。
  • 檢查是否收斂,如果收斂則停止迭代。

_get_cluster_labels(self, clusters)

將樣本分配給它們所屬的簇,並返回樣本的標籤。

_create_clusters(self, centroids)

根據最近的中心點將樣本分配到不同的簇中。

_closest_centroid(self, sample, centroids)

計算樣本到中心點的最短歐幾里得距離,以確定它屬於哪個簇。

_get_centroids(self, clusters)

計算每個簇的新中心點,即該簇中所有樣本的平均值。

_is_converged(self, centroids_old, centroids)

檢查中心點是否收斂,即新中心點和舊中心點之間的距離是否為零。

plot(self)

用於將簇和中心點的圖形可視化。

cent(self)

返回計算出的中心點。

Image preprocess

image = cv2.imread("/kaggle/input/cute-cat/cat.jpg")

def gmm_preprocess(image):
    # convert image to 2D array
    pixel_values = image.reshape((-1, 3))
    pixel_values = pixel_values/255
    return pixel_values

image讀取我們想呈現的圖片

https://ithelp.ithome.com.tw/upload/images/20230915/201528210hdQvj7DM8.jpg

並將image轉成二維陣列

Image using GMM

k_slot = [2, 3, 7, 20]

for k_idx in k_slot:
    k = KMeans(K=k_idx, max_iters=100)
    print("K = ", k_idx)
    pixel_values = gmm_preprocess(image)
    pixel_values = pixel_values[:, [2, 1, 0]]
    y_pred = k.predict(pixel_values)
    centers = np.uint8(k.cent())
    model_GMM = GaussianMixture(k=k_idx, max_iter=100)
    pi, mu, cov = model_GMM.init(pixel_values, parameters=(centers, y_pred))
    likelihood = model_GMM.EM(pi, mu, cov)
    # normalize likelihood
    likelihood = likelihood / np.sum(likelihood, axis=0)
    model_GMM.plot_image(image, likelihood)

使用 KMeans 和 Gaussian Mixture Model 來處理一張圖像。

過程中,建立一個K均值(KMeans)的模型,設定K值為k_idx,最大迭代次數為100。

對圖像進行一些預處理,可能包括轉換顏色通道等操作。

使用 KMeans 對處理後的像素值進行聚類,得到每個像素的分類結果y_pred

計算 KMeans 的中心點centers

之後建立一個 GMM 模型(高斯混合模型),同樣設定K值為k_idx,最大迭代次數為100。

進行 GMM 模型的初始化,使用先前得到的 KMeans的中心點和分類結果。

使用期望最大化(EM)算法對GMM模型進行擬合,得到每個像素屬於不同高斯分布的機率。

正規化機率,確保總和為1。

最後,使用GMM模型將圖像可視化,根據像素屬於不同高斯分布的機率,可能會產生一個有趣的圖像效果。


https://chart.googleapis.com/chart?cht=tx&chl=K%3D2

https://ithelp.ithome.com.tw/upload/images/20230915/20152821ZUxvgMSaHv.png

https://chart.googleapis.com/chart?cht=tx&chl=K%3D3

https://ithelp.ithome.com.tw/upload/images/20230915/201528214Dspa5ebeM.png

https://chart.googleapis.com/chart?cht=tx&chl=K%3D7

https://ithelp.ithome.com.tw/upload/images/20230915/20152821wyaVr0Af6j.png

https://chart.googleapis.com/chart?cht=tx&chl=K%3D20

https://ithelp.ithome.com.tw/upload/images/20230915/20152821V8c6rUrg29.png


詳細 Notebook 可以參考 Kaggle

明天要進入真實實戰部份,會透過 Kaggle Dataset - Mall Customer Segmentation Data 來演練

/images/emoticon/emoticon06.gif


上一篇
[Day 14] Gaussian Mixture Model — 背後理論
下一篇
[Day 16] Gaussian Mixture Model — 解決真實問題
系列文
ML From Scratch31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言